package br3akr.DeadlyDinnerParty.Server.World;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import br3akr.DeadlyDinnerParty.Server.Actions.MultiStepAction;
import br3akr.DeadlyDinnerParty.Server.Actions.ScryAction;
import br3akr.DeadlyDinnerParty.Server.Exceptions.DeadlyDinnerPartyException;
import br3akr.DeadlyDinnerParty.Server.World.Exit.ExitStatus;

public class Area {	
	private Set<Exit> exits;
	private Map<Exit.ExitDirections, Exit> exitDirs;
	private Set<GameCharacter> characters;
	private Set<Body> bodies;
	private Set<Item> items;
	private Set<Chest> chests;
	private String name;
	private String description;
	private Set<GameCharacter> listeners;
	private Set<GameCharacter> scryers;
	
	public Area(String name, String desc) {
		this.name = name;
		this.description = desc;
		this.items = new HashSet<Item>();
		this.characters = new HashSet<GameCharacter>();
		this.exits = new HashSet<Exit>();
		this.exitDirs = new HashMap<Exit.ExitDirections, Exit>();
		this.listeners = new HashSet<GameCharacter>();
		this.chests = new HashSet<Chest>();
		this.bodies = new HashSet<Body>();
		this.scryers = new HashSet<GameCharacter>();
	}

	public void addExit(Exit exit, Exit.ExitDirections exitDir) {
		exits.add(exit);
		exitDirs.put(exitDir, exit);
	}
	
	public void listenExit(Exit.ExitDirections exitDirection, GameCharacter gc) throws DeadlyDinnerPartyException {
		Exit listenToExit = exitDirs.get(exitDirection);
		if (listenToExit == null) {
			throw new DeadlyDinnerPartyException("There is no exit in that direction to listen to.");
		}	
		listenToExit.getExitArea(this);
	}
	
	public Exit.ExitDirections getDirectionToArea(Area toArea) {
		Exit.ExitDirections retval = null;
		
		for (Exit curExit : exits) {
			Area testArea = curExit.getExitArea(this);
			if (testArea.equals(toArea)) {
				retval = curExit.getExitDirection(this);
			}
		}
		
		return retval;
	}
	
	public Exit getExitToArea(Area toArea) {
		Exit retval = null;
		
		for (Exit curExit : exits) {
			Area testArea = curExit.getExitArea(this);
			if (testArea.equals(toArea)) {
				retval = curExit;
			}
		}
		
		return retval;
	}
	
	public Exit getExit(Exit.ExitDirections exitDir) {
		return exitDirs.get(exitDir);
	}
	
	public ExitStatus getExitStatus(Exit.ExitDirections direction) {
		return exitDirs.get(direction).getStatus();
	}
	
	public Area getAreaByExit(Exit.ExitDirections exit) {
		return exitDirs.get(exit).getExitArea(this);
	}
	
	public String getDescription() {
		return description;
	}
	
	public Set<Exit> getExits() {
		return exits;
	}
	
	public Set<GameCharacter> getCharacters() {
		return characters;
	}
	
	public void addCharacter(GameCharacter character) {
		characters.add(character);
	}
	
	public void removeCharacter(GameCharacter character) {
		characters.remove(character);
	}
	
	public Set<Item> getItems() {
		return items;
	}
	
	public void addItem(Item item) {
		items.add(item);
	}
	
	public void removeItem(Item item) {
		if (item != null) {
			items.remove(item);
		}
	}
	
	public void removeBody(Body body) {
		if (body != null) {
			bodies.remove(body);
		}
	}
	
	public Item getItem(String name) throws DeadlyDinnerPartyException {
		Item retval = null;
		
		for (Item i : items) {
			if (i.getName().toUpperCase().equals(name.toUpperCase())) {
				retval = i;
				break;
			} else if (i.getName().toUpperCase().startsWith(name.toUpperCase())) {
				if (retval == null) {
					retval = i;
				} else {
					throw new DeadlyDinnerPartyException("Which item?");
				}
			}
		}
		return retval;
	}
	
	public GameCharacter getCharacter(String name) throws DeadlyDinnerPartyException {
		GameCharacter retval = null;
		
		for (GameCharacter gc : characters) {
			if (gc.getName().toUpperCase().equals(name.toUpperCase())) {
				retval = gc;
				break;
			} else if (gc.getName().toUpperCase().startsWith(name.toUpperCase())) {
				if (retval == null) {
					retval = gc;
				} else {
					throw new DeadlyDinnerPartyException("Which character?");
				}
			}
		}
		return retval;
	}
	
	public Body getBody(String name) throws DeadlyDinnerPartyException {
		Body retval = null;
		String curName;
		name = name.toUpperCase();
		for (Body b : bodies) {
			curName = b.getCharacter().getName().toUpperCase();
			if (curName.equals(name.toUpperCase())) {
				retval = b;
				break;
			} else if (curName.startsWith(name.toUpperCase())) {
				if (retval == null) {
					retval = b;
				} else {
					throw new DeadlyDinnerPartyException("Which character?");
				}
			}
		}
		return retval;
	}
	
	public String describeRoom(GameCharacter actor) {
		StringBuilder newDesc = new StringBuilder(name);
		String headerText = null;
		newDesc.append("\n" + description);
		
		if (characters.size() > 1) {
			headerText = "\n\nPeople here: ";
			for (GameCharacter curCharacter : characters) {
				if ((actor == null || !actor.equals(curCharacter)) && !curCharacter.isHiddenFrom(actor)) {
					if (newDesc.indexOf(headerText) < 0) {
						newDesc.append(headerText);
					}
					
					if (curCharacter.getCurrentAction() != null &&
							(curCharacter.getCurrentAction() instanceof MultiStepAction) &&
							!((MultiStepAction)curCharacter.getCurrentAction()).isComplete()) {
						newDesc.append("\n  " + ((MultiStepAction)curCharacter.getCurrentAction()).getCurrentDescription());
					} else {
						newDesc.append("\n  " + curCharacter.getName() + " " + curCharacter.getDescription());
					}
				}
			}
		}
		
		if (!items.isEmpty()) {
			headerText = "\n\nItems here: ";
			for (Item curItem : items) {
				if (!curItem.isHiddenFrom(actor)) {
					if (newDesc.indexOf(headerText) < 0) {
						newDesc.append(headerText);
					}
					newDesc.append("\n  " + curItem.getName());
					newDesc.append(" - " + curItem.getDescription());
				}
			}
		}
		
		if (!bodies.isEmpty()) {
			headerText = "\n\nBodies here: ";
			for (Body curBody : bodies) {
				if (!curBody.isHiddenFrom(actor)) {
					if (newDesc.indexOf(headerText) < 0) {
						newDesc.append(headerText);
					}
					newDesc.append("\n  " + curBody.getDescription(0));
				}
			}
		}
		
		if (!chests.isEmpty()) {
			newDesc.append("\n\nChests here:");
			for (Chest c : chests) {
				newDesc.append("\n  " + c.getName());
				newDesc.append(" - " + c.getDescription());
			}
		}
		
		if (exits.size() > 0) {
			newDesc.append("\n\nExits Here: ");
			for (Exit curExit : exits) {
				String exitDesc = null;
				if ((curExit.isHidden() && curExit.knowsSecretExit(actor)) || !curExit.isHidden()) {
					if (curExit.getStatus() == ExitStatus.OPEN) {
						exitDesc = "open";
					} else if (curExit.getStatus() == ExitStatus.CLOSED || curExit.getStatus() == ExitStatus.LOCKED) {
						exitDesc = "closed";
					} else if (curExit.getStatus() == ExitStatus.BASHED) {
						exitDesc = "smashed to pieces";
					} else {
						exitDesc = "in an unknown status";
					}
					newDesc.append("\n  The ");
					if (curExit.isHidden()) {
						newDesc.append("concealed ");
					}
					newDesc.append("exit " + curExit.getExitDirection(this) + " to " + curExit.getExitArea(this).getName() + " is " + exitDesc + ".");
				}
			}
		} else {
			newDesc.append("\nThere are no apparent exits.");
		}
		return newDesc.toString();
	}
	
	public Set<GameCharacter> getAreaOnlookers(GameCharacter actingCharacter, Set<GameCharacter> targetCharacters) {
		Set<GameCharacter> onlookers = new HashSet<GameCharacter>();
		for (GameCharacter curChar : characters) {
			if (!curChar.equals(actingCharacter) 
					&& (targetCharacters == null || !targetCharacters.contains(curChar)) 
					&& !actingCharacter.isHiddenFrom(curChar)) {
				
				if (!(curChar.getCurrentAction() instanceof ScryAction) || 
						((curChar.getCurrentAction() instanceof ScryAction) 
								&& ((ScryAction)curChar.getCurrentAction()).isComplete())) {
					onlookers.add(curChar);					
				}
			}
		}
		for (GameCharacter curChar : scryers) {
			if (!curChar.equals(actingCharacter) && (targetCharacters == null || !targetCharacters.contains(curChar)) && !actingCharacter.isHiddenFrom(curChar)) {
				onlookers.add(curChar);
			}
		}
		return onlookers;
	}
	
	public String getName() {
		return name;
	}
	
	public void addListener(GameCharacter gc) {
		listeners.add(gc);
	}
	
	public boolean isListener(GameCharacter gc) {
		for (GameCharacter g : listeners) {
			if (g.equals(gc)) {
				return true;
			}
		}
		
		return false;
	}
	
	public void removeListener(GameCharacter gc) {
		listeners.remove(gc);
	}
	
	public Set<GameCharacter> getListeners() {
		return listeners;
	}
	
	public void addChest(Chest chest) {
		chests.add(chest);
	}
	
	public Chest getChest(String name) {
		Chest retval = null;
		for (Chest c : chests) {
			if (c.getName().toUpperCase().startsWith(name.toUpperCase())) {
				if (retval == null) {
					retval = c;
				} else {
					retval = null;
					break;
				}
			}
		}
		return retval;
	}

	public Set<Body> getBodies() {
		return bodies;
	}

	public void addBody(Body body) {
		this.bodies.add(body);
	}
	
	public void addScryer(GameCharacter c) {
		scryers.add(c);
	}
	
	public void removeScryer(GameCharacter c) {
		scryers.remove(c);
	}
	
	public Set<GameCharacter> getScryers() {
		return scryers;
	}
}